#!/usr/bin/python
# Used to generate C# classes which support multiple dispatch for a
# particular signature.
from optparse import OptionParser
import os
import sys

version = "0.3"		# if this is changed update the tar target in the Makefile as well

class GenerateDispatch:
	def __init__(self, path, options):
		self.__name = options.Name
		self.__namespace = options.Namespace
		self.__args = [a.partition(':') for a in options.Args]
		self.__result = options.Result
		self.__visibility = options.Visibility
		self.__uses = options.Uses
		self.__comment = options.Comment
		self.__indent = False
		self.__file = open(path, "w")
		
	def generate(self):
		self.__writeHeader()
		self.__writeUsing()
		self.__writeClassHeader()
		self.__writeAttribute()
		self.__writeInvoke()
		self.__writeNextMethod()
		self.__writeRegister()
		self.__writePrivateTypes()
		self.__writePrivateMethods()
		self.__writeFields()
		self.__writeClassTrailer()
		self.__file.flush()
		
	def __needsQuotes(self, str, start):
		for i in xrange(start, len(str)):
			c = str[i]
			if not c.isalnum():
				if c != ':' and c != '_':
					return True
		return False
	
	def __quoteArg(self, arg):
		i = arg.find('=')
		if i >= 0 and self.__needsQuotes(arg, i+1):
			return arg[0:i+1] + "'" + arg[i+1:] + "'"
		else:
			return arg
			
	def __writeHeader(self):
		self.__write("// Machine generated by multimethod-sharp %s using" % version)
		args = [self.__quoteArg(a) for a in sys.argv]
		self.__write("// %s" % " ".join(args))
		
	def __writeUsing(self):
		uses = ["System", "System.Collections.Generic", "System.Reflection"]
		if self.__uses:
			uses.extend(self.__uses)
		uses.sort()
		
		for u in uses:
			self.__write("using %s;" % u)
		self.__write("")
		
	def __writeClassHeader(self):
		if self.__namespace:
			self.__write("namespace %s" % self.__namespace)
			self.__write("{")
			self.__indent = True
		if self.__comment:
			for s in self.__comment:
				if s.startswith("//"):
					self.__write("%s" % s)		# this allows people to use doc comments
				else:
					self.__write("// %s" % s)
		else:
			self.__write("// Dynamically dispatches to the best %s overload." % self.__name)
		self.__write("%s static class %s" % (self.__visibility, self.__name))
		self.__write("{")
		
	def __writeAttribute(self):
		self.__write("	// Used to identify overloads. The overload method name may be anything")
		self.__write("	// but the signature should match Invoke except that both the return and")
		self.__write("	// argument types may be derived versions of the types that appear in Invoke.")
		self.__write("	[Serializable]")
		self.__write("	[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]")
		self.__write("	%s sealed class OverloadAttribute : Attribute" % self.__visibility)
		self.__write("	{")
		self.__write("	}")
		self.__write("	")
		
	def __writeInvoke(self):
		sig = ", ".join([a[0] + ' ' + a[2] for a in self.__args])
		
		args = ", ".join([a[2] for a in self.__args])
		types = ", ".join([a[2] + ".GetType()" for a in self.__args])
		
		self.__write("	// Calls the best overload for the actual argument types or throws System.")
		self.__write("	// MissingMethodException or System.Reflection.AmbiguousMatchException.")
		self.__write("	// Arguments may be null.")
		self.__write("	public static %s Invoke(%s)" % (self.__result, sig))
		self.__write("	{")
		self.__write("		if (ms_candidates == null)")
		self.__write("			DoAutoRegister();")
		self.__write("		")
		self.__write("		var __actual = new Entry(%s);" % args)
		self.__write("		int __index = DoGetNextMethod(__actual, 0);")
		self.__write("		if (__index == -1)")
		self.__write('			throw new MissingMethodException(string.Format("Invoke failed to find an overload for %s({0}).", __actual));' % (self.__name))
		self.__write("		")
		self.__write("		ms_nextIndex = DoGetNextMethod(__actual, __index + 1);")
		self.__write("		if (ms_nextIndex > 0)")
		self.__write("			if (DoLeftPrecedesRight(ms_candidates[__index], ms_candidates[ms_nextIndex]) == 0)")
		self.__write('				throw new AmbiguousMatchException(string.Format("Invoke found ambiguous overloads for %s({0}).", __actual));' % (self.__name))
		self.__write("		")
		if self.__result == "void":
			self.__write("		ms_candidates[__index].Method(%s);" % args)
		else:
			self.__write("		return ms_candidates[__index].Method(%s);" % args)
		self.__write("	}")
		self.__write("	")
		
	def __writeNextMethod(self):
		sig = ", ".join([a[0] + ' ' + a[2] for a in self.__args])
		
		args = ", ".join([a[2] for a in self.__args])
		types = ", ".join([a[2] + ".GetType()" for a in self.__args])
			
		self.__write("	// Calls the next best overload. For example, if overload Foo(Derived) was last")
		self.__write("	// called then this would call Foo(Base). The arguments should normally be the")
		self.__write("	// same as the ones passed into the original Invoke call. May throw System.")
		self.__write("	// MissingMethodException or System.Reflection.AmbiguousMatchException.")
		self.__write("	public static %s NextMethod(%s)" % (self.__result, sig))
		self.__write("	{")
		self.__write("		var __actual = new Entry(%s);" % args)
		self.__write("		")
		self.__write("		int __index = ms_nextIndex;")
		self.__write("		if (__index == -1)")
		self.__write('			throw new MissingMethodException(string.Format("NextMethod failed to find an overload for %s({0}).", __actual));' % (self.__name))
		self.__write("		")
		self.__write("		ms_nextIndex = DoGetNextMethod(__actual, __index + 1);")
		self.__write("		if (ms_nextIndex > 0)")
		self.__write("			if (DoLeftPrecedesRight(ms_candidates[__index], ms_candidates[ms_nextIndex]) == 0)")
		self.__write('				throw new AmbiguousMatchException(string.Format("NextMethod found ambiguous overloads for %s({0}).", __actual));' % (self.__name))
		self.__write("		")
		if self.__result == "void":
			self.__write("		ms_candidates[__index].Method(%s);" % args)
		else:
			self.__write("		return ms_candidates[__index].Method(%s);" % args)
		self.__write("	}")
		self.__write("	")
		
	def __writeRegister(self):
		args = ["a" + str(i + 1) for i in xrange(len(self.__args))]
		sigs = [self.__args[i][0] + ' ' + "a" + str(i + 1) for i in xrange(len(self.__args))]
		
		if self.__result == "void":
			cast = ""
		else:
			cast = "(%s) " % self.__result
		
		self.__write("	// Manually registers overload methods in the specified type. Note that if")
		self.__write("	// this is not called then all types and methods in the loaded assemblies")
		self.__write("	// are registered the first time Invoke is called.")
		self.__write("	public static void Register(Type type)")
		self.__write("	{")
		self.__write("		if (ms_candidates == null)")
		self.__write("			ms_candidates = new List<Entry>();")
		self.__write("		")
		self.__write("		int oldCount = ms_candidates.Count;")
		self.__write("		foreach (MethodInfo info in type.GetMethods(MethodBinding))")
		self.__write("		{")
		self.__write("			if (info.IsDefined(typeof(OverloadAttribute), false))")
		self.__write("			{")
		self.__write("				DoCheckMethod(info);")
		self.__write("				")
		self.__write("				MethodInfo temp = info;			// need this so foreach doesn't hose the lambdas")
		self.__write("				ParameterInfo[] parms = info.GetParameters();")
		self.__write("				Entry entry;")
		self.__write("				if (info.IsStatic)")
		self.__write("				{")
		self.__write("					entry = new Entry(")
		self.__write("						(%s) => %stemp.Invoke(null, new object[]{%s})," % (", ".join(sigs), cast, ", ".join(args)))
		self.__write("						%s);" % ", ".join(["parms[%s].ParameterType" % i for i in xrange(len(self.__args))]))
		self.__write("				}")
		self.__write("				else")
		self.__write("				{")
		self.__write("					entry = new Entry(")
		if len(args) > 1:
			self.__write("						(%s) => %stemp.Invoke(a1, new object[]{%s})," % (", ".join(sigs), cast, ", ".join(args[1:])))
			self.__write("						temp.DeclaringType, %s);" % ", ".join(["parms[%s].ParameterType" % i for i in xrange(len(self.__args) - 1)]))
		else:
			self.__write("						(%s) => %stemp.Invoke(a1, new object[0])," % (", ".join(sigs), cast))
			self.__write("						temp.DeclaringType);")
		self.__write("				}")
		self.__write("				")
		self.__write("				if (ms_candidates.IndexOf(entry) >= 0)")
		self.__write('					throw new InvalidOperationException(string.Format("There is already an overload registered with the same signature as {0}.{1}.", info.DeclaringType.Name, info.Name));')
		self.__write("				")
		self.__write("				ms_candidates.Add(entry);")
		self.__write("			}")
		self.__write("		}")
		self.__write("		")
		self.__write("		if (oldCount < ms_candidates.Count)")
		self.__write("			DoSortCandidates();")
		self.__write("	}")
		self.__write("	")
	
	def __writePrivateTypes(self):
		sig = ", ".join([a[0] + ' ' + a[2] for a in self.__args])
		
		types = [a[0] for a in self.__args]
		keySig = ["Type type" + str(i + 1) for i in xrange(len(self.__args))]
		
		self.__write("	#region Private Types")
		self.__write("	private delegate %s OverloadType(%s);" % (self.__result, sig))
		self.__write("	")
		self.__write("	private struct Entry : IEquatable<Entry>")
		self.__write("	{")
		self.__write("		public Entry(%s) : this()" % sig)
		self.__write("		{")
		self.__write("			Method = null;")
		for i in xrange(len(self.__args)):
			self.__write("			Type%s = (object) %s != null ? %s.GetType() : null;" % (i+1, self.__args[i][2], self.__args[i][2]))
		self.__write("		}")
		self.__write("		")
		self.__write("		public Entry(OverloadType overload, %s) : this()" % ", ".join(keySig))
		self.__write("		{")
		self.__write("			Method = overload;")
		for i in xrange(len(self.__args)):
			self.__write("			Type%s = type%s;" % (i+1, i+1))
		self.__write("		}")
		self.__write("		")
		self.__write("		public OverloadType Method {get; private set;}")
		self.__write("		")
		for i in xrange(len(self.__args)):
			self.__write("		public Type Type%s {get; private set;}" % (i + 1))
			self.__write("		")
		self.__write("		public override string ToString()")
		self.__write("		{")
		self.__write("			var builder = new System.Text.StringBuilder();")
		self.__write("			")
		for i in xrange(len(self.__args)):
			self.__write('			builder.Append(Type%s != null ? Type%s.ToString() : "null");' % (i+1, i+1))
			if i+1 < len(self.__args):
				self.__write('			builder.Append(", ");')
		self.__write("			")
		self.__write("			return builder.ToString();")
		self.__write("		}")
		self.__write("		")
		self.__write("		public override bool Equals(object obj)")
		self.__write("		{")
		self.__write("			if (obj == null)")
		self.__write("				return false;")
		self.__write("			")
		self.__write("			if (GetType() != obj.GetType())")
		self.__write("				return false;")
		self.__write("			")
		self.__write("			Entry rhs = (Entry) obj;")
		self.__write("			return this == rhs;")
		self.__write("		}")
		self.__write("		")
		self.__write("		public bool Equals(Entry rhs)")
		self.__write("		{")
		self.__write("			return this == rhs;")
		self.__write("		}")
		self.__write("		")
		self.__write("		public static bool operator==(Entry lhs, Entry rhs)")
		self.__write("		{")
		for i in xrange(len(self.__args)):
			self.__write("			if (lhs.Type%s != rhs.Type%s)" % (i+1, i+1))
			self.__write("				return false;")
			self.__write("			")
		self.__write("			return true;")
		self.__write("		}")
		self.__write("		")
		self.__write("		public static bool operator!=(Entry lhs, Entry rhs)")
		self.__write("		{")
		self.__write("			return !(lhs == rhs);")
		self.__write("		}")
		self.__write("		")
		self.__write("		public override int GetHashCode()")
		self.__write("		{")
		self.__write("			int hash = 0;")
		self.__write("			")
		self.__write("			unchecked")
		self.__write("			{")
		for i in xrange(len(self.__args)):
			self.__write("				hash += Type%s.GetHashCode();" % (i + 1))
		self.__write("			}")
		self.__write("			")
		self.__write("			return hash;")
		self.__write("		}")
		self.__write("	}")
		self.__write("	#endregion")
		self.__write("	")
		
	def __writePrivateMethods(self):
		self.__write("	#region Private Members")
		self.__write("	private const BindingFlags MethodBinding = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;")
		self.__write("	")
		self.__write("	private static int DoGetNextMethod(Entry actual, int startIndex)")
		self.__write("	{")
		self.__write("		for (int index = startIndex; index < ms_candidates.Count; ++index)")
		self.__write("		{")
		self.__write("			if (DoCanBeCalledWith(ms_candidates[index], actual))")
		self.__write("				return index;")
		self.__write("		}")
		self.__write("		")
		self.__write("		return -1;")
		self.__write("	}")
		self.__write("	")
		self.__write("	// This and Entry.ToString are the only places where we need to worry about null types.")
		self.__write("	private static bool DoCanBeCalledWith(Entry formal, Entry actual)")
		self.__write("	{")
		for i in xrange(len(self.__args)):
			self.__write("		if (actual.Type%s != null && !formal.Type%s.IsAssignableFrom(actual.Type%s))" % (i + 1, i + 1, i + 1))
			self.__write("			return false;")
			self.__write("		")
		self.__write("		return true;")
		self.__write("	}")
		self.__write("	")
		self.__write("	// Sort order doesn't depend on actual arguments so we can do it once.")
		self.__write("	private static void DoSortCandidates()")
		self.__write("	{")
		self.__write("		// Note that we cannot use List.Sort because DoLeftPrecedesRight")
		self.__write("		// does not obey the transitivity requirement of a comparison sort.")
		self.__write("		// So, we'll instead use a crappy O(N^2) algorithm which should")
		self.__write("		// not be too bad because there will hardly ever be a lot of overloads.")
		self.__write("		for (int i = 0; i < ms_candidates.Count; ++i)")
		self.__write("		{")
		self.__write("			for (int j = i + 1; j < ms_candidates.Count; ++j)")
		self.__write("			{")
		self.__write("				if (DoLeftPrecedesRight(ms_candidates[i], ms_candidates[j]) > 0)")
		self.__write("				{")
		self.__write("					Entry temp = ms_candidates[i];")
		self.__write("					ms_candidates[i] = ms_candidates[j];")
		self.__write("					ms_candidates[j] = temp;")
		self.__write("				}")
		self.__write("			}")
		self.__write("		}")
		self.__write("	}")
		self.__write("	")
		self.__write("	// An overload A is better than overload B if A has an argument which is")
		self.__write("	// more specialized than the corresponding argument in B and B has no")
		self.__write("	// arguments which are more specialized. An overload A is ambiguous")
		self.__write("	// with an overload B if A has a better argument than B and B has a")
		self.__write("	// better argument than A.")
		self.__write("	private static int DoLeftPrecedesRight(Entry left, Entry right)")
		self.__write("	{")
		for i in xrange(len(self.__args)):
			self.__write("		bool leftPrecedes%s = left.Type%s.IsSubclassOf(right.Type%s);" % (i+1, i+1, i+1))
		self.__write("		")
		for i in xrange(len(self.__args)):
			self.__write("		bool rightPrecedes%s = right.Type%s.IsSubclassOf(left.Type%s);" % (i+1, i+1, i+1))
		self.__write("		")
		lhs = ["leftPrecedes" + str(i+1) for i in xrange(len(self.__args))]
		rhs = ["!rightPrecedes" + str(i+1) for i in xrange(len(self.__args))]
		self.__write("		if ((%s) && %s)" % (" || ".join(lhs), " && ".join(rhs)))
		self.__write("			return -1;")
		lhs = ["rightPrecedes" + str(i+1) for i in xrange(len(self.__args))]
		rhs = ["!leftPrecedes" + str(i+1) for i in xrange(len(self.__args))]
		self.__write("		else if ((%s) && %s)" % (" || ".join(lhs), " && ".join(rhs)))
		self.__write("			return +1;")
		self.__write("		else")
		self.__write("			return 0;")
		self.__write("	}")
		self.__write("	")
		self.__write("	private static void DoAutoRegister()")
		self.__write("	{")
		self.__write("		ms_candidates = new List<Entry>();		// we want to set this even if no overloads are found")
		self.__write("		")
		self.__write("		foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())")
		self.__write("		{")
		self.__write("			foreach (Type type in assembly.GetTypes())")
		self.__write("			{")
		self.__write("				foreach (MethodInfo method in type.GetMethods(MethodBinding))")
		self.__write("				{")
		self.__write("					if (method.IsDefined(typeof(OverloadAttribute), false))")
		self.__write("					{")
		self.__write("						Register(type);")
		self.__write("						break;")
		self.__write("					}")
		self.__write("				}")
		self.__write("			}")
		self.__write("		}")
		self.__write("	}")
		self.__write("	")
		self.__write("	public static void DoCheckMethod(MethodInfo info)")
		self.__write("	{")
		self.__write("		// Covariant return types are not very useful in the context of multimethods,")
		self.__write("		// but may be useful if the overload is called directly.")
		self.__write("		if (!typeof(%s).IsAssignableFrom(info.ReturnType))" % (self.__result))
		self.__write('			throw new InvalidOperationException(string.Format("{0}.{1} has return type {2} but should have %s (or a derived class).", info.DeclaringType.Name, info.Name, info.ReturnType));' % (self.__result))
		self.__write("		")
		self.__write("		ParameterInfo[] parms = info.GetParameters();")
		self.__write("		if (info.IsStatic)")
		self.__write("		{")
		self.__write("			if (parms.Length != %s)" % len(self.__args))
		self.__write('				throw new InvalidOperationException(string.Format("{0}.{1} has arity {2} but should have arity %s.", info.DeclaringType.Name, info.Name, parms.Length));' % (len(self.__args)))
		self.__write("			")
		for i in xrange(len(self.__args)):
			self.__write("			if (!typeof(%s).IsAssignableFrom(parms[%s].ParameterType))" % (self.__args[i][0], i))
			self.__write('				throw new InvalidOperationException(string.Format("{0}.{1} argument {2} is not compatible with type %s.", info.DeclaringType.Name, info.Name, parms[%s].Name));' % (self.__args[i][0], i))
		self.__write("		}")
		self.__write("		else")
		self.__write("		{")
		self.__write("			if (parms.Length != %s)" % (len(self.__args) - 1))
		self.__write('				throw new InvalidOperationException(string.Format("{0}.{1} has arity {2} but should have arity %s.", info.DeclaringType.Name, info.Name, parms.Length));' % (len(self.__args) - 1))
		self.__write("			")
		self.__write("			if (!typeof(%s).IsAssignableFrom(info.DeclaringType))" % (self.__args[0][0]))
		self.__write('				throw new InvalidOperationException(string.Format("{0}.{1} this argument is not compatible with type %s.", info.DeclaringType.Name, info.Name));' % (self.__args[0][0]))
		self.__write("			")
		for i in xrange(1, len(self.__args)):
			self.__write("			if (!typeof(%s).IsAssignableFrom(parms[%s].ParameterType))" % (self.__args[i][0], i - 1))
			self.__write('				throw new InvalidOperationException(string.Format("{0}.{1} argument {2} is not compatible with type %s.", info.DeclaringType.Name, info.Name, parms[%s].Name));' % (self.__args[i][0], i - 1))
		self.__write("		}")
		self.__write("	}")
		self.__write("	#endregion")
		self.__write("	")
		
	def __writeFields(self):
		self.__write("	#region Fields")
		self.__write("	private static List<Entry> ms_candidates;	// best methods are first")
		self.__write("	private static int ms_nextIndex;")
		self.__write("	#endregion")
		
	def __writeClassTrailer(self):
		self.__write("}")
		if self.__namespace:
			self.__indent = False
			self.__write("}")
		
	def __write(self, s):
		if self.__indent:
			self.__file.write("\t")
		self.__file.write(s)
		self.__file.write("\n")

usage = "usage: %prog --result=TYPE --name=CLASS --arg=TYPE1:NAME1 [options]"
parser = OptionParser(usage = usage, description = "--arg, --comment, and --using may appear multiple times.")
parser.add_option("--arg", dest = "Args", action = "append", metavar = "TYPE:NAME", help = "a multimethod argument")
parser.add_option("--comment", dest = "Comment", action = "append", metavar = "LINE", help = "custom class comment")
parser.add_option("--dst", dest = "Dest", metavar = "DIR", help = "where to put the generated file, defaults to the working directory")
parser.add_option("--name", dest = "Name", metavar = "CLASS", help = "the name of the multimethod class")
parser.add_option("--namespace", dest = "Namespace", metavar = "NAME", help = "namespace enclosing the class, defaults to the global namespace")
parser.add_option("--result", dest = "Result", metavar = "TYPE", help = "type of the method result")
parser.add_option("--using", dest = "Uses", action = "append", metavar = "NAMESPACE", help = "a custom using declaration")
parser.add_option("--version", dest = "Version", action = "store_true", default = False, help = "print the version number and exit")
parser.add_option("--visibility", dest = "Visibility", default = "internal", metavar = "VALUE", help = "class visibility, defaults to internal")

(options, args) = parser.parse_args()

if options.Version:
	print "multimethod-sharp %s" % version
elif options.Name and options.Args and options.Result:
	for arg in options.Args:
		if ':' not in arg:
			sys.stderr.write("--arg values must be formatted as type:name.\n")
			sys.exit(1)
	
	if options.Dest:
		path = os.path.join(options.Dest, options.Name + ".cs")
	else:
		path = options.Name + ".cs"
	
	dispatch = GenerateDispatch(path, options)
	dispatch.generate()
else:
	sys.stderr.write("Missing required ")
	if not options.Name:
		sys.stderr.write("--name ")
	if not options.Args:
		sys.stderr.write("--arg ")
	if not options.Result:
		sys.stderr.write("--result ")
	sys.stderr.write("argument(s).\n")
	sys.exit(1)
